
<!doctype html>
<html lang="en" class="no-js">
  <head>
    
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1">
      
      
      
      
        <link rel="prev" href="../../DevelopGuide/CMSIS_RTOS_API_Use_Guide/">
      
      
        <link rel="next" href="../How_To_Use_Esp8266_Tencent_Firmware_Connect_Iot_Explorer/">
      
      <link rel="icon" href="../../../assets/favicon.png">
      <meta name="generator" content="mkdocs-1.4.3, mkdocs-material-9.1.19">
    
    
      
        <title>无线通信模组AT框架开发指南 - TobudOS Documentation</title>
      
    
    
      <link rel="stylesheet" href="../../../assets/stylesheets/main.eebd395e.min.css">
      
        
        <link rel="stylesheet" href="../../../assets/stylesheets/palette.ecc896b0.min.css">
      
      

    
    
    
      
        
        
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
        <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
      
    
    
    <script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
    
      

    
    
    
  </head>
  
  
    
    
      
    
    
    
    
    <body dir="ltr" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="indigo">
  
    
    
      <script>var palette=__md_get("__palette");if(palette&&"object"==typeof palette.color)for(var key of Object.keys(palette.color))document.body.setAttribute("data-md-color-"+key,palette.color[key])</script>
    
    <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
    <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
    <label class="md-overlay" for="__drawer"></label>
    <div data-md-component="skip">
      
        
        <a href="#1" class="md-skip">
          Skip to content
        </a>
      
    </div>
    <div data-md-component="announce">
      
    </div>
    
    
      

<header class="md-header" data-md-component="header">
  <nav class="md-header__inner md-grid" aria-label="Header">
    <a href="../../" title="TobudOS Documentation" class="md-header__button md-logo" aria-label="TobudOS Documentation" data-md-component="logo">
      
  <img src="../../../assets/logo.png" alt="logo">

    </a>
    <label class="md-header__button md-icon" for="__drawer">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
    </label>
    <div class="md-header__title" data-md-component="header-title">
      <div class="md-header__ellipsis">
        <div class="md-header__topic">
          <span class="md-ellipsis">
            TobudOS Documentation
          </span>
        </div>
        <div class="md-header__topic" data-md-component="header-topic">
          <span class="md-ellipsis">
            
              无线通信模组AT框架开发指南
            
          </span>
        </div>
      </div>
    </div>
    
      
        <form class="md-header__option" data-md-component="palette">
          
            
            
            
            <input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="indigo"  aria-label="Switch to dark mode"  type="radio" name="__palette" id="__palette_1">
            
              <label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
              </label>
            
          
            
            
            
            <input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="indigo"  aria-label="Switch to light mode"  type="radio" name="__palette" id="__palette_2">
            
              <label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12c0-2.42-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
              </label>
            
          
        </form>
      
    
    
      <div class="md-header__option">
        <div class="md-select">
          
          <button class="md-header__button md-icon" aria-label="Select language">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04M18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12m-2.62 7 1.62-4.33L19.12 17h-3.24Z"/></svg>
          </button>
          <div class="md-select__inner">
            <ul class="md-select__list">
              
                <li class="md-select__item">
                  <a href="../../../AT_Framwork_Guide/AT_Firmware_and_SAL_Firmware_User_Guide/" hreflang="zh" class="md-select__link">
                    中文
                  </a>
                </li>
              
                <li class="md-select__item">
                  <a href="./" hreflang="en" class="md-select__link">
                    English
                  </a>
                </li>
              
            </ul>
          </div>
        </div>
      </div>
    
    
      <label class="md-header__button md-icon" for="__search">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
      </label>
      <div class="md-search" data-md-component="search" role="dialog">
  <label class="md-search__overlay" for="__search"></label>
  <div class="md-search__inner" role="search">
    <form class="md-search__form" name="search">
      <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
      <label class="md-search__icon md-icon" for="__search">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
      </label>
      <nav class="md-search__options" aria-label="Search">
        
          <a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7 0-.24-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91 1.61 0 2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08Z"/></svg>
          </a>
        
        <button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
        </button>
      </nav>
      
        <div class="md-search__suggest" data-md-component="search-suggest"></div>
      
    </form>
    <div class="md-search__output">
      <div class="md-search__scrollwrap" data-md-scrollfix>
        <div class="md-search-result" data-md-component="search-result">
          <div class="md-search-result__meta">
            Initializing search
          </div>
          <ol class="md-search-result__list" role="presentation"></ol>
        </div>
      </div>
    </div>
  </div>
</div>
    
    
      <div class="md-header__source">
        <a href="https://atomgit.com/tobudos/Document" title="Go to repository" class="md-source" data-md-component="source">
  <div class="md-source__icon md-icon">
    
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  </div>
  <div class="md-source__repository">
    Atomgit
  </div>
</a>
      </div>
    
  </nav>
  
</header>
    
    <div class="md-container" data-md-component="container">
      
      
        
          
            
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
  <div class="md-grid">
    <ul class="md-tabs__list">
      
        
  
  


  <li class="md-tabs__item">
    <a href="../../" class="md-tabs__link">
      Home
    </a>
  </li>

      
        
  
  


  <li class="md-tabs__item">
    <a href="../../Introduction/TobudOS_Brief_Introduction/" class="md-tabs__link">
      项目特性简介
    </a>
  </li>

      
        
  
  


  
  
  
    <li class="md-tabs__item">
      <a href="../../Kernel_Porting/Porting_Manual_for_KEIL/" class="md-tabs__link">
        内核移植指南
      </a>
    </li>
  

      
        
  
  


  
  
  
    <li class="md-tabs__item">
      <a href="../../DevelopGuide/Development_Manual/" class="md-tabs__link">
        内核开发指南
      </a>
    </li>
  

      
        
  
  
    
  


  
  
  
    <li class="md-tabs__item">
      <a href="./" class="md-tabs__link md-tabs__link--active">
        AT模组框架
      </a>
    </li>
  

      
        
  
  


  <li class="md-tabs__item">
    <a href="../../ElfLoader_Guide/ElfLoader_Quick_Start/" class="md-tabs__link">
      ElfLoader动态加载
    </a>
  </li>

      
        
  
  


  
  
  
    <li class="md-tabs__item">
      <a href="../../OTA_Guide/OTA_Quick_Start_based_EVB_MX_Plus/" class="md-tabs__link">
        固件升级
      </a>
    </li>
  

      
        
  
  


  <li class="md-tabs__item">
    <a href="../../MicroPython_Guide/BearPi_MicroPython_QuickStart/" class="md-tabs__link">
      MicroPython开发指南
    </a>
  </li>

      
        
  
  


  <li class="md-tabs__item">
    <a href="../../VFS_Guide/vfs/" class="md-tabs__link">
      文件系统
    </a>
  </li>

      
        
  
  


  <li class="md-tabs__item">
    <a href="../../GUI_Guide/gui/" class="md-tabs__link">
      GUI开发
    </a>
  </li>

      
        
  
  


  
  
  
    <li class="md-tabs__item">
      <a href="../../DevelopKit/1.EVB_MX_Plus_Quick_Start/" class="md-tabs__link">
        官方开发套件
      </a>
    </li>
  

      
    </ul>
  </div>
</nav>
          
        
      
      <main class="md-main" data-md-component="main">
        <div class="md-main__inner md-grid">
          
            
              
              <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
                <div class="md-sidebar__scrollwrap">
                  <div class="md-sidebar__inner">
                    

  


<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
  <label class="md-nav__title" for="__drawer">
    <a href="../../" title="TobudOS Documentation" class="md-nav__button md-logo" aria-label="TobudOS Documentation" data-md-component="logo">
      
  <img src="../../../assets/logo.png" alt="logo">

    </a>
    TobudOS Documentation
  </label>
  
    <div class="md-nav__source">
      <a href="https://atomgit.com/tobudos/Document" title="Go to repository" class="md-source" data-md-component="source">
  <div class="md-source__icon md-icon">
    
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  </div>
  <div class="md-source__repository">
    Atomgit
  </div>
</a>
    </div>
  
  <ul class="md-nav__list" data-md-scrollfix>
    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../" class="md-nav__link">
        Home
      </a>
    </li>
  

    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../Introduction/TobudOS_Brief_Introduction/" class="md-nav__link">
        项目特性简介
      </a>
    </li>
  

    
      
      
      

  
  
  
    
    <li class="md-nav__item md-nav__item--nested">
      
      
      
      
      <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
      
      
        
          
        
          
        
          
        
      
      
        <label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
          内核移植指南
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
        <label class="md-nav__title" for="__nav_3">
          <span class="md-nav__icon md-icon"></span>
          内核移植指南
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../Kernel_Porting/Porting_Manual_for_KEIL/" class="md-nav__link">
        基于Keil移植TobudOS内核
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../Kernel_Porting/Porting_Manual_for_IAR/" class="md-nav__link">
        基于IAR 移植TobudOS内核
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../Kernel_Porting/Porting_Manual_for_GCC/" class="md-nav__link">
        基于GCC 移植TobudOS内核
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
      
      
      

  
  
  
    
    <li class="md-nav__item md-nav__item--nested">
      
      
      
      
      <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" >
      
      
        
          
        
          
        
          
        
      
      
        <label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
          内核开发指南
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
        <label class="md-nav__title" for="__nav_4">
          <span class="md-nav__icon md-icon"></span>
          内核开发指南
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopGuide/Development_Manual/" class="md-nav__link">
        TobudOS内核开发指南
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopGuide/API_Reference/" class="md-nav__link">
        TobudOS内核API参考手册
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopGuide/CMSIS_RTOS_API_Use_Guide/" class="md-nav__link">
        TobudOS内核CMSIS适配API指南
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
      
      
      

  
  
    
  
  
    
    <li class="md-nav__item md-nav__item--active md-nav__item--nested">
      
      
      
      
      <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" checked>
      
      
        
          
        
          
        
      
      
        <label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="0">
          AT模组框架
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="true">
        <label class="md-nav__title" for="__nav_5">
          <span class="md-nav__icon md-icon"></span>
          AT模组框架
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
    
  
  
    <li class="md-nav__item md-nav__item--active">
      
      <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
      
      
        
      
      
        <label class="md-nav__link md-nav__link--active" for="__toc">
          无线通信模组AT框架开发指南
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <a href="./" class="md-nav__link md-nav__link--active">
        无线通信模组AT框架开发指南
      </a>
      
        

<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
  
  
  
    
  
  
    <label class="md-nav__title" for="__toc">
      <span class="md-nav__icon md-icon"></span>
      Table of contents
    </label>
    <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
      
        <li class="md-nav__item">
  <a href="#11" class="md-nav__link">
    1.1. 百花绽放的通信模组
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#12-at" class="md-nav__link">
    1.2. AT 指令集
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#13-at" class="md-nav__link">
    1.3. AT 指令解析方式
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#14-at-sal" class="md-nav__link">
    1.4. AT 框架与 SAL 层
  </a>
  
</li>
      
    </ul>
  
</nav>
      
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../How_To_Use_Esp8266_Tencent_Firmware_Connect_Iot_Explorer/" class="md-nav__link">
        基于AT框架和定制模组如何对接腾讯云IoT Explorer平台
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../ElfLoader_Guide/ElfLoader_Quick_Start/" class="md-nav__link">
        ElfLoader动态加载
      </a>
    </li>
  

    
      
      
      

  
  
  
    
    <li class="md-nav__item md-nav__item--nested">
      
      
      
      
      <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_7" >
      
      
        
          
        
          
        
      
      
        <label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="0">
          固件升级
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
        <label class="md-nav__title" for="__nav_7">
          <span class="md-nav__icon md-icon"></span>
          固件升级
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../OTA_Guide/OTA_Quick_Start_based_EVB_MX_Plus/" class="md-nav__link">
        基于EVB_MX的固件升级指南
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../OTA_Guide/In_Application_Programming_based_EVB_MX_Plus/" class="md-nav__link">
        基于EVB_MX的IAP开发指南
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../MicroPython_Guide/BearPi_MicroPython_QuickStart/" class="md-nav__link">
        MicroPython开发指南
      </a>
    </li>
  

    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../VFS_Guide/vfs/" class="md-nav__link">
        文件系统
      </a>
    </li>
  

    
      
      
      

  
  
  
    <li class="md-nav__item">
      <a href="../../GUI_Guide/gui/" class="md-nav__link">
        GUI开发
      </a>
    </li>
  

    
      
      
      

  
  
  
    
    <li class="md-nav__item md-nav__item--nested">
      
      
      
      
      <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_11" >
      
      
        
          
        
          
        
          
        
          
        
          
        
          
        
          
        
      
      
        <label class="md-nav__link" for="__nav_11" id="__nav_11_label" tabindex="0">
          官方开发套件
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_11_label" aria-expanded="false">
        <label class="md-nav__title" for="__nav_11">
          <span class="md-nav__icon md-icon"></span>
          官方开发套件
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/1.EVB_MX_Plus_Quick_Start/" class="md-nav__link">
        EVB_MX+（STM32L4）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/2.EVB_LX_RISC-V_Quick_Start/" class="md-nav__link">
        EVB_LX（GD32V RISC-V）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/3.EVB_WL_Quick_Start/" class="md-nav__link">
        EVB_WL（STM32WL）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/4.EVB_G0_QuickStart/" class="md-nav__link">
        EVB_G0（STM32G0）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/5.EVB_AIoT_NXP_QuickStart/" class="md-nav__link">
        EVB_AIoT_NXP（RT1062）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/6.EVB_CH32V_QuickStart/" class="md-nav__link">
        EVB_CH32V（CH32V307）快速上手
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../DevelopKit/7.EVB_AIoT_STM32_QuickStart/" class="md-nav__link">
        EVB_AIoT_STM32（STM32H7）快速上手
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
  </ul>
</nav>
                  </div>
                </div>
              </div>
            
            
              
              <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
                <div class="md-sidebar__scrollwrap">
                  <div class="md-sidebar__inner">
                    

<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
  
  
  
    
  
  
    <label class="md-nav__title" for="__toc">
      <span class="md-nav__icon md-icon"></span>
      Table of contents
    </label>
    <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
      
        <li class="md-nav__item">
  <a href="#11" class="md-nav__link">
    1.1. 百花绽放的通信模组
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#12-at" class="md-nav__link">
    1.2. AT 指令集
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#13-at" class="md-nav__link">
    1.3. AT 指令解析方式
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#14-at-sal" class="md-nav__link">
    1.4. AT 框架与 SAL 层
  </a>
  
</li>
      
    </ul>
  
</nav>
                  </div>
                </div>
              </div>
            
          
          
            <div class="md-content" data-md-component="content">
              <article class="md-content__inner md-typeset">
                
                  

  
    <a href="https://atomgit.com/tobudos/Document/edit/master/docs/AT_Framwork_Guide/AT_Firmware_and_SAL_Firmware_User_Guide.md" title="Edit this page" class="md-content__button md-icon">
      
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25Z"/></svg>
    </a>
  
  
    
      
    
    <a href="https://atomgit.com/tobudos/Document/raw/master/docs/AT_Framwork_Guide/AT_Firmware_and_SAL_Firmware_User_Guide.md" title="View source of this page" class="md-content__button md-icon">
      
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0 8a5 5 0 0 1-5-5 5 5 0 0 1 5-5 5 5 0 0 1 5 5 5 5 0 0 1-5 5m0-12.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5Z"/></svg>
    </a>
  


<h1 id="1">1. 概述</h1>
<h2 id="11">1.1. 百花绽放的通信模组</h2>
<p>随着嵌入式系统终端对网络的需求越来越多，各种通信模组百花齐放，几乎覆盖了所有的网络接入方式：WIFI、2G、4G Cat.1、4G Cat.4、NB-IoT、LoRa……未来还会有更多。</p>
<p>对于嵌入式系统终端而言，这些通信模组屏蔽了网络接入方式的差异化，无论使用哪种方式接入网络，设备仅仅需要提供一个串口与模组交互即可。</p>
<p>交互双方有了通信的硬件基础（串口），还需要制定和遵循一套有效的软件协议，目前大多数模组厂商都采用 AT 指令集。</p>
<h2 id="12-at">1.2. AT 指令集</h2>
<p>AT 指令有两种：</p>
<p>① 普通 AT 指令：以 AT 开头、换行符结束的一组字符串，每个指令执行成功与否都有相应的返回。</p>
<div class="highlight"><pre><span></span><code>---&gt; AT

&lt;--- OK
</code></pre></div>
<p>②其他的一些非预期的信息，模块将有对应的一些信息提示，主动发送：</p>
<div class="highlight"><pre><span></span><code>&lt;--- +IPD:...

&lt;--- +URC:...
</code></pre></div>
<p>AT指令交互类似服务器/客户端架构，一般来说模组侧作为AT服务端，MCU侧作为AT客户端，交互方式对应也有两种：</p>
<ul>
<li>客户端发出一条 AT 指令，服务端收到处理之后返回结果给客户端；</li>
<li>服务端主动发送数据给客户端，客户端被动接收处理；</li>
</ul>
<p>整体架构如下图：</p>
<p><img alt="AT architecture" src="../assets/AT_Firmware/AT_architecture.png" /></p>
<p>模组的功能非常丰富，有修改模组配置的 AT 指令、有 TCP/IP 协议栈通信的 AT 指令、甚至还有 MQTT、HTTP、NTP 协议栈的通信指令，这些指令合在一起构成了该模组的 AT 指令集。一般来说，同一家厂商的不同模组之间，AT 指令集差异不大，而不同厂商之间的 AT 指令集之间差异较大。</p>
<h2 id="13-at">1.3. AT 指令解析方式</h2>
<p>AT 指令有三种解析方式：</p>
<ul>
<li>裸机直接在串口中断处理函数中解析</li>
</ul>
<p>因为解析时间未知，所以这种解析方式最不可取，极容易出现数据丢失问题。</p>
<ul>
<li>裸机使用 ringbuff（环形缓冲区）缓存数据，在 main 函数中构造状态机解析</li>
</ul>
<p>串口每来一个字符就送入缓冲区，最大程度保证数据不会丢失，这种解析方式随着 main 函数中其它业务逻辑的增多，导致缓冲区数据迟迟得不到解析，依然会出现问题。</p>
<ul>
<li>RTOS 使用 ringbuff 缓存数据，创建一个任务专门用于数据解析</li>
</ul>
<p>同样，串口每来一个字符就送入缓冲区，保证数据不丢失，只要数据解析任务的优先级够高，数据总是会被及时解析，大幅提升系统的实时性能。</p>
<h2 id="14-at-sal">1.4. AT 框架与 SAL 层</h2>
<p>什么是 AT 框架？其实并不神秘~</p>
<p>AT框架是RTOS官方人员/社区开发者编写的一个通用AT指令解析任务，使开发者只需要调用 AT 框架提供的 API 即可处理与模组的交互数据。</p>
<p>SAL 框架全称 Socket Abstract Layer，提供了类似 socket 网络编程的抽象层。基于 AT 框架实现 SAL 的底层函数叫做通信模组的驱动程序。</p>
<h1 id="2-tobudos-at">2. TobudOS的 AT 框架</h1>
<h2 id="21">2.1. 整体架构</h2>
<p><img alt="AT_SAL_architecture" src="../assets/AT_Firmware/AT_SAL_architecture.png" /></p>
<h2 id="22">2.2. 实现原理</h2>
<p>TobudOSAT 框架的实现在 <code>net/at</code> 目录下的 <code>tos_at.h</code>和<code>tos_at.c</code>两个文件中。</p>
<p>① AT 框架所有接收数据的数据流向如图所示：</p>
<ul>
<li>串口中断中逐个字节接收，写入 chr_fifo 缓冲区；</li>
<li>解析任务 at_parser 从 chr_fifo 缓冲区中逐个字节读取，读取一行数据到 recv_cache（行缓冲区）并进行处理；</li>
<li>处理之后如果不是模组上报的普通的数据，也不是 AT 命令期望的返回结果，也不是 "OK"、"FAIL"、"ERROR"，则为普通数据，将行缓冲区的数据复制到用户传入的 echo_buffer 中，由用户处理。</li>
</ul>
<p><img alt="AT_data_flow" src="../assets/AT_Firmware/AT_data_flow.png" /></p>
<p>② AT 框架将模组主动上报的数据作为事件，将上报数据的数据头和用户指定的回调处理函数作为事件表，在 AT 框架初始化时注册。</p>
<p>比如 ESP8266 在 TCP/IP 通信时，收到远程服务器发送来的数据时会使用+IPD 头主动上报数据，将此事件注册的示例如下：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* esp8266.c */</span>

<span class="n">at_event_t</span><span class="w"> </span><span class="n">esp8266_at_event</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">{</span><span class="w"> </span><span class="s">&quot;+IPD,&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">esp8266_incoming_data_process</span><span class="w"> </span><span class="p">},</span>
<span class="p">};</span>
</code></pre></div>
<p>注册之后，每次行解析的时候都会判断是否为事件头，如果是则证明有事件发生，拉起注册的回调函数进行处理。</p>
<blockquote>
<p>注意，AT 框架只从 chr_fifo 中读取出了事件头，事件头之后的所有数据依然在缓冲区中，所在开发者在编写回调函数时可以边读出数据，边解析数据。</p>
</blockquote>
<p>③ 如何完成一次交互？</p>
<p>AT 框架将每一次交互抽象为一个 at_echo_t 对象，用户交互的流程如下：</p>
<p><img alt="AT_exec_flow" src="../assets/AT_Firmware/AT_exec_flow.png" /></p>
<p>④ 如何实现多个 channel 同时存在？</p>
<p>大多数通信模组在进行 TCP/IP 通信时，支持同时创建多个 socket 通信（一般为 6 个），作为一个通用的 AT 框架，也为了更好的上层 SAL 服务，AT 框架也相应的支持多 channel。</p>
<p>每个 channel 对象如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">at_data_channel_st</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kt">uint8_t</span><span class="w">             </span><span class="n">is_free</span><span class="p">;</span>
<span class="w">    </span><span class="n">k_chr_fifo_t</span><span class="w">        </span><span class="n">rx_fifo</span><span class="p">;</span>
<span class="w">    </span><span class="kt">uint8_t</span><span class="w">            </span><span class="o">*</span><span class="n">rx_fifo_buffer</span><span class="p">;</span>
<span class="w">    </span><span class="n">k_mutex_t</span><span class="w">           </span><span class="n">rx_lock</span><span class="p">;</span>

<span class="w">    </span><span class="n">at_channel_status_t</span><span class="w"> </span><span class="n">status</span><span class="p">;</span>

<span class="w">    </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w">         </span><span class="o">*</span><span class="n">remote_ip</span><span class="p">;</span>
<span class="w">    </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w">         </span><span class="o">*</span><span class="n">remote_port</span><span class="p">;</span>
<span class="p">}</span><span class="w"> </span><span class="n">at_data_channel_t</span><span class="p">;</span>
</code></pre></div>
<p>每次上层发起 Socket Connect 连接时，将此 Socket 的 ip 和 port 绑定到 channel 对象，然后动态申请一块内存作为该 channnel 的接收缓冲区，当 Socket close 时，随即释放此缓冲区。</p>
<h2 id="23-tobudosat">2.3. TobudOSAT 框架参数配置</h2>
<p>AT 框架的所有缓冲区内存都是使用动态内存，内部机制使用到了信号量 sem、互斥锁 mutex、字符流队列 chr_fifo、计时表 stopwatch，所以请首先保证在<code>tos_config.h</code>中这些配置处于使能模式，其中动态内存池的大小可以根据随后 AT 框架的配置修改：</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define TOS_CFG_MUTEX_EN                    1u</span>

<span class="cp">#define TOS_CFG_SEM_EN                      1u</span>

<span class="cp">#define TOS_CFG_MMHEAP_EN                   1u</span>

<span class="cp">#define TOS_CFG_MMHEAP_DEFAULT_POOL_EN      1u</span>

<span class="cp">#define TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE    0x8000</span>
</code></pre></div>
<p>AT 框架的所有可配置选项都已在<code>tos_at.h</code>中使用宏定义给出，可以根据自己的需要进行裁剪配置：</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define AT_DATA_CHANNEL_NUM                     6</span>
<span class="cp">#define AT_DATA_CHANNEL_FIFO_BUFFER_SIZE        (2048 + 1024)</span>

<span class="cp">#define AT_UART_RX_FIFO_BUFFER_SIZE             (2048 + 1024)</span>
<span class="cp">#define AT_RECV_CACHE_SIZE                      2048</span>

<span class="cp">#define AT_CMD_BUFFER_SIZE                      512</span>

<span class="cp">#define AT_PARSER_TASK_STACK_SIZE               2048</span>
<span class="cp">#define AT_PARSER_TASK_PRIO                     2</span>
</code></pre></div>
<p>配置项的意义如下：</p>
<table>
<thead>
<tr>
<th align="center">配置项</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">AT_DATA_CHANNEL_NUM</td>
<td align="center">AT 框架支持的最大通道数</td>
</tr>
<tr>
<td align="center">AT_DATA_CHANNEL_FIFO_BUFFER_SIZE</td>
<td align="center">每个通道的缓冲区大小</td>
</tr>
<tr>
<td align="center">AT_UART_RX_FIFO_BUFFER_SIZE</td>
<td align="center">串口接收缓冲区大小</td>
</tr>
<tr>
<td align="center">AT_RECV_CACHE_SIZE</td>
<td align="center">行缓冲区大小</td>
</tr>
<tr>
<td align="center">AT_CMD_BUFFER_SIZE</td>
<td align="center">命令缓冲区大小</td>
</tr>
<tr>
<td align="center">AT_PARSER_TASK_STACK_SIZE</td>
<td align="center">解析任务的任务栈大小</td>
</tr>
<tr>
<td align="center">AT_PARSER_TASK_PRIO</td>
<td align="center">解析任务的任务优先级</td>
</tr>
</tbody>
</table>
<h2 id="24-at-api">2.4. AT 框架提供的 API</h2>
<ul>
<li>AT 框架写入一个字节数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">tos_at_uart_input_byte</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">data</span><span class="p">);</span>
</code></pre></div>
<p>此 API 通常在串口中断中调用。</p>
<ul>
<li>AT 框架初始化</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_init</span><span class="p">(</span><span class="n">hal_uart_port_t</span><span class="w"> </span><span class="n">uart_port</span><span class="p">,</span><span class="w"> </span><span class="n">at_event_t</span><span class="w"> </span><span class="o">*</span><span class="n">event_table</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">event_table_size</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">uart_port</td>
<td align="center">AT 框架使用的串口</td>
</tr>
<tr>
<td align="center">event_table</td>
<td align="center">事件表地址</td>
</tr>
<tr>
<td align="center">event_table_size</td>
<td align="center">事件表大小</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>创建一个 at_echo_t 对象</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_echo_create</span><span class="p">(</span><span class="n">at_echo_t</span><span class="w"> </span><span class="o">*</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_size</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">echo_expect</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">echo</td>
<td align="center">at_echo_t 对象句柄</td>
</tr>
<tr>
<td align="center">buffer</td>
<td align="center">用于保存命令执行结果的缓冲区</td>
</tr>
<tr>
<td align="center">buffer_size</td>
<td align="center">缓冲区大小</td>
</tr>
<tr>
<td align="center">echo_expect</td>
<td align="center">期望的字符串</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>执行一条 AT 命令，timeout 超时后才返回</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_cmd_exec</span><span class="p">(</span><span class="n">at_echo_t</span><span class="w"> </span><span class="o">*</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">cmd</span><span class="p">,</span><span class="w"> </span><span class="p">...);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">echo</td>
<td align="center">at_echo_t 对象句柄</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">命令执行之后的等待时间</td>
</tr>
<tr>
<td align="center">cmd</td>
<td align="center">要执行的 AT 命令</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>执行一条 AT 命令，一旦有期望结果立马返回，若无，则 timeout 超时后返回</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_cmd_exec_until</span><span class="p">(</span><span class="n">at_echo_t</span><span class="w"> </span><span class="o">*</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">cmd</span><span class="p">,</span><span class="w"> </span><span class="p">...);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">echo</td>
<td align="center">at_echo_t 对象句柄</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">命令执行之后的等待时间</td>
</tr>
<tr>
<td align="center">cmd</td>
<td align="center">要执行的 AT 命令</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>发送十六进制原始数据，timeout 超时后才返回</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_raw_data_send</span><span class="p">(</span><span class="n">at_echo_t</span><span class="w"> </span><span class="o">*</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">size</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">echo</td>
<td align="center">at_echo_t 对象句柄</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">命令执行之后的等待时间</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">待发送的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">缓冲区大小</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>发送十六进制数据，一旦有期望结果立马返回，若无，则 timeout 超时后返回</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_raw_data_send_until</span><span class="p">(</span><span class="n">at_echo_t</span><span class="w"> </span><span class="o">*</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">size</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">echo</td>
<td align="center">at_echo_t 对象句柄</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">命令执行之后的等待时间</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">待发送的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">缓冲区大小</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>直接从串口接收缓冲区中读取数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_uart_read</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">buf</td>
<td align="center">存放读取数据的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">读取数据的长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>直接从串口接收缓冲区中读取一行数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_uart_readline</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">buf</td>
<td align="center">存放读取数据的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">读取数据的长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>申请一个 channel</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_channel_alloc</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">port</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">ip</td>
<td align="center">socket ip</td>
</tr>
<tr>
<td align="center">port</td>
<td align="center">socket port</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：非负值、通道 ID，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>写入数据到 channel 的接收缓冲区</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_channel_write</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">channel_id</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">channel_id</td>
<td align="center">已经申请成功的 channel 通道 ID</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">存放写入数据的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">写入数据的长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<blockquote>
<p>在TCP/IP通信时，解析完AT指令上报的数据时，就可以将真正网络接收的数据调用此API写入channle的接收缓冲区。</p>
</blockquote>
<ul>
<li>从 channel 的接收缓冲区中读取数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_channel_read</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">channel_id</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">channel_id</td>
<td align="center">已经申请成功的 channel 通道 ID</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">存放读取数据的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">读取数据的长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>从 channel 的接收缓冲区中读取数据，有超时时间</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">__API__</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">tos_at_channel_read_timed</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">channel_id</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="o">*</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">buffer_len</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">意义</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">channel_id</td>
<td align="center">已经申请成功的 channel 通道 ID</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">存放读取数据的缓冲区</td>
</tr>
<tr>
<td align="center">size</td>
<td align="center">读取数据的长度</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">读取等待时间</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<h1 id="3-tobudos-sal">3. TobudOS的 SAL 框架</h1>
<h2 id="31-sal">3.1. 什么是 SAL 框架</h2>
<p>SAL 框架全称 Socket Abstract Layer，提供了类似 socket 网络编程的抽象层，为上层应用一层统一的网络 API，屏蔽了底层不同通信模组/方式的差异。</p>
<h2 id="32-sal">3.2. SAL 框架的实现原理</h2>
<p>TobudOSSAL 框架的实现在<code>net/sal_module_wrapper</code>路径中，仅有两个文件：<code>sal_module_wrapper.h</code>和<code>sal_module_wrapper.c</code>。</p>
<p>SAL 框架的底层是一套函数指针，如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">sal_module_st</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">get_local_mac</span><span class="p">)(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">mac</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">get_local_ip</span><span class="p">)(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">gw</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">mask</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">parse_domain</span><span class="p">)(</span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">host_name</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">host_ip</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">host_ip_len</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">connect</span><span class="p">)(</span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">port</span><span class="p">,</span><span class="w"> </span><span class="n">sal_proto_t</span><span class="w"> </span><span class="n">proto</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">send</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">recv_timeout</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">recv</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">sendto</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">port</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">recvfrom</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">recvfrom_timeout</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">);</span>

<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="n">close</span><span class="p">)(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">);</span>
<span class="p">}</span><span class="w"> </span><span class="n">sal_module_t</span><span class="p">;</span>
</code></pre></div>
<p>不同的通信模组驱动都去实现这一套函数指针即可，TobudOS官方已经提供了非常多的通信模组驱动实现 SAL 框架，覆盖常用的通信方式，比如 2G、4G Cat.4、4G Cat.1、NB-IoT 等，在<code>devices</code>文件夹下：</p>
<ul>
<li>air724</li>
<li>bc26</li>
<li>bc25_28_95</li>
<li>bc35_28_95_lwm2m</li>
<li>ec20</li>
<li>esp8266</li>
<li>m26</li>
<li>m5310a</li>
<li>m6312</li>
<li>sim800a</li>
<li>sim7600ce</li>
<li>欢迎贡献更多驱动...</li>
</ul>
<h2 id="33-sal-api">3.3. SAL 框架提供的网络编程 API</h2>
<ul>
<li>注册该模组实现到 SAL 框架</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_register</span><span class="p">(</span><span class="n">sal_module_t</span><span class="w"> </span><span class="o">*</span><span class="n">module</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">module</td>
<td align="center">模组实现的函数指针结构体句柄</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>模组初始化</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>域名解析</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_parse_domain</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">host_name</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">host_ip</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">host_ip_len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">host_name</td>
<td align="center">域名</td>
</tr>
<tr>
<td align="center">host_ip</td>
<td align="center">存放解析出的 ip 缓冲区</td>
</tr>
<tr>
<td align="center">host_ip_len</td>
<td align="center">缓冲区大小</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>建立 TCP/UDP socket 连接</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_connect</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">port</span><span class="p">,</span><span class="w"> </span><span class="n">sal_proto_t</span><span class="w"> </span><span class="n">proto</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">ip</td>
<td align="center">目的主机 ip</td>
</tr>
<tr>
<td align="center">port</td>
<td align="center">目的主机 port</td>
</tr>
<tr>
<td align="center">proto</td>
<td align="center">协议类型：TOS_SAL_PROTO_TCP 或者 TOS_SAL_PROTO_UDP</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：socket id，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>TCP socket 发送数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_send</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">sock</td>
<td align="center">socket id</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">待发送数据</td>
</tr>
<tr>
<td align="center">len</td>
<td align="center">待发送数据长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：实际发送数据的长度，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>TCP socket 接收数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_recv</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>
<span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_recv_timeout</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">sock</td>
<td align="center">socket id</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">存放接收数据的缓冲区</td>
</tr>
<tr>
<td align="center">len</td>
<td align="center">缓冲区长度</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">等待超时时间</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：实际读取数据的长度，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>UDP socket 发送数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_sendto</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">ip</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">port</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">sock</td>
<td align="center">socket id</td>
</tr>
<tr>
<td align="center">ip</td>
<td align="center">目的主机 ip</td>
</tr>
<tr>
<td align="center">port</td>
<td align="center">目的主机 port</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">待发送数据</td>
</tr>
<tr>
<td align="center">len</td>
<td align="center">待发送数据长度</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：实际发送数据的长度，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>UDP socket 接收数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_recvfrom</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">);</span>
<span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_recvfrom_timeout</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">len</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">timeout</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">sock</td>
<td align="center">socket id</td>
</tr>
<tr>
<td align="center">buf</td>
<td align="center">存放接收数据的缓冲区</td>
</tr>
<tr>
<td align="center">len</td>
<td align="center">缓冲区长度</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">等待超时时间</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：实际读取数据的长度，失败：-1</td>
</tr>
</tbody>
</table>
<ul>
<li>关闭 socket</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">tos_sal_module_close</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">sock</span><span class="p">);</span>
</code></pre></div>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">sock</td>
<td align="center">socket id</td>
</tr>
<tr>
<td align="center">返回值</td>
<td align="center">成功：0，失败：-1</td>
</tr>
</tbody>
</table>
<h1 id="4-at-sal">4. AT 框架+SAL 框架移植方法</h1>
<h2 id="41">4.1. 移植前的准备</h2>
<p>本文中我使用官方 EVB_MX+开发板为例，主控芯片为 STM32L431RCT6，USART1 用于 printf 打印日志信息，LPUART1 用于和通信模组交互，通信模组使用 WIFI 模组 ESP8266 为例。</p>
<p>移植 AT 框架前需要准备好一个移植好 TobudOS内核的工程，可以做到串口的正常收发，这里我使用 TobudOS中<code>TobudOS\board\TobudOS_EVB_MX_Plus\KEIL\hello_world</code>中的工程。</p>
<h2 id="42-at">4.2. 移植 AT 框架</h2>
<p>TobudOSAT 框架的实现在 <code>net/at</code> 目录下的 <code>tos_at.h</code>和<code>tos_at.c</code>两个文件中，TobudOSAT 框架底层使用的串口驱动 HAL 层在<code>platform\hal\st\stm32l4xx\src</code>目录下的文件<code>tos_hal_uart.c</code>中，头文件在<code>kernel\hal\include</code>路径中。</p>
<p>首先将这两个 c 文件添加到 Keil 工程中：</p>
<p><img alt="add_at_file_to_project" src="../assets/AT_Firmware/add_at_file_to_project.png" /></p>
<p>然后将头文件路径添加到 Keil MDK 中：</p>
<p><img alt="add_at_path_to_project" src="../assets/AT_Firmware/add_at_path_to_project.png" /></p>
<p>然后在串口中断中配置调用 AT 框架的字节接收函数，编辑<code>stm32l4xx_it.c</code>文件：</p>
<p>① 添加 AT 框架的头文件：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* Private includes ----------------------------------------------------------*/</span>
<span class="cm">/* USER CODE BEGIN Includes */</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&quot;tos_at.h&quot;</span>
<span class="cm">/* USER CODE END Includes */</span>
</code></pre></div>
<p>② 在文件最后添加串口中断回调函数：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* USER CODE BEGIN 1 */</span>
<span class="kt">void</span><span class="w"> </span><span class="nf">HAL_UART_RxCpltCallback</span><span class="p">(</span><span class="n">UART_HandleTypeDef</span><span class="w"> </span><span class="o">*</span><span class="n">huart</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="k">extern</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">data</span><span class="p">;</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">huart</span><span class="o">-&gt;</span><span class="n">Instance</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">LPUART1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">HAL_UART_Receive_IT</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hlpuart1</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span>
<span class="w">        </span><span class="n">tos_at_uart_input_byte</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* USER CODE END 1 */</span>
</code></pre></div>
<p>注意，在回调函数中声明 data 变量在外部定义，这是因为 STM32 HAL 库的机制，需要在初始化完成之后先调用一次串口接收函数，使能串口接收中断，编辑<code>usart.c</code>文件：</p>
<p>① 在文件开头定义 data 变量为全局变量：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* USER CODE BEGIN 0 */</span>
<span class="kt">uint8_t</span><span class="w"> </span><span class="n">data</span><span class="p">;</span>
<span class="cm">/* USER CODE END 0 */</span>
</code></pre></div>
<p>② 在串口初始化完成之后使能接收中断：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* LPUART1 init function */</span>

<span class="kt">void</span><span class="w"> </span><span class="nf">MX_LPUART1_UART_Init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Instance</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LPUART1</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">BaudRate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">115200</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">WordLength</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_WORDLENGTH_8B</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">StopBits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_STOPBITS_1</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">Parity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_PARITY_NONE</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">Mode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_MODE_TX_RX</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">HwFlowCtl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_HWCONTROL_NONE</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">Init</span><span class="p">.</span><span class="n">OneBitSampling</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_ONE_BIT_SAMPLE_DISABLE</span><span class="p">;</span>
<span class="w">    </span><span class="n">hlpuart1</span><span class="p">.</span><span class="n">AdvancedInit</span><span class="p">.</span><span class="n">AdvFeatureInit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UART_ADVFEATURE_NO_INIT</span><span class="p">;</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">HAL_UART_Init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hlpuart1</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">HAL_OK</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">    </span><span class="n">Error_Handler</span><span class="p">();</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="c1">//手动添加，使能串口中断</span>
<span class="w">    </span><span class="n">HAL_UART_Receive_IT</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hlpuart1</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="43-sal">4.3. 移植 SAL 框架</h2>
<p>TobudOSSAL 框架的实现在<code>net/sal_module_wrapper</code>路径中，仅有两个文件：<code>sal_module_wrapper.h</code>和<code>sal_module_wrapper.c</code>。</p>
<p>将 c 文件添加到 Keil MDK 工程中：</p>
<p><img alt="add_sal_file_to_project" src="../assets/AT_Firmware/add_sal_file_to_project.png" /></p>
<p>将头文件所在路径添加到 Keil MDK 中：</p>
<p><img alt="add_sal_path_to_project" src="../assets/AT_Firmware/add_sal_path_to_project.png" /></p>
<h2 id="44">4.4. 移植通信模组驱动</h2>
<p>TobudOS官方已经提供了非常多的通信模组驱动实现 SAL 框架，覆盖常用的通信方式，比如 2G、4G Cat.4、4G Cat.1、NB-IoT 等，在<code>devices</code>文件夹下，：</p>
<ul>
<li>air724</li>
<li>bc26</li>
<li>bc25_28_95</li>
<li>bc35_28_95_lwm2m</li>
<li>ec20</li>
<li>esp8266</li>
<li>m26</li>
<li>m5310a</li>
<li>m6312</li>
<li>sim800a</li>
<li>sim7600ce</li>
<li>欢迎贡献更多驱动...</li>
</ul>
<p>因为这些驱动都是 SAL 框架的实现，所以这些通信模组的驱动可以根据实际硬件情况<strong>选择一种加入到工程中</strong>，这里我以 WIFI 模组 ESP8266 为例，演示如何加入通信模组驱动到工程中。</p>
<p>ESP8266 的驱动在<code>devices\esp8266</code>目录中。</p>
<p>首先将<code>esp8266.c</code>文件加入到 Keil MDK 工程中：</p>
<p><img alt="add_devices_file_to_project" src="../assets/AT_Firmware/add_devices_file_to_project.png" /></p>
<p>然后将<code>esp8266.h</code>头文件所在路径添加到 Keil MDK 工程中：</p>
<p><img alt="add_devices_path_to_project" src="../assets/AT_Firmware/add_devices_path_to_project.png" /></p>
<p>移植完成。</p>
<h2 id="45">4.5. 测试网络通信</h2>
<p>移植完成之后，可直接使用官方提供的示例代码进行测试，测试双 socket 进行 TCP 通信的测试程序为<code>examples\tcp_through_module\tcp_through_module.c</code>。</p>
<p>将工程中的 helloworld 示例代码更换为该文件，如图：</p>
<p><img alt="add_example_file_to_project" src="../assets/AT_Firmware/add_example_file_to_project.png" /></p>
<p>在这个示例中，首先通过宏定义来配置当前使用的是哪个模组：</p>
<p><img alt="tcp_example_module_config" src="../assets/AT_Firmware/tcp_example_module_config.png" /></p>
<p>然后调用对应模组的初始化函数：</p>
<p><img alt="tcp_example_module_init" src="../assets/AT_Firmware/tcp_example_module_init.png" /></p>
<p>此处需要注意：初始化模组时指定的串口号即为 AT 通信模组所使用的串口，在<code>tos_hal_uart.h</code>中定义：</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">enum</span><span class="w"> </span><span class="n">hal_uart_port_en</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">    </span><span class="c1">//对应 LPUART1</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_1</span><span class="p">,</span><span class="w">        </span><span class="c1">//对应 USART1</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_2</span><span class="p">,</span><span class="w">        </span><span class="c1">//依此类推</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_3</span><span class="p">,</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_4</span><span class="p">,</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_5</span><span class="p">,</span>
<span class="w">    </span><span class="n">HAL_UART_PORT_6</span><span class="p">,</span>
<span class="p">}</span><span class="w"> </span><span class="n">hal_uart_port_t</span><span class="p">;</span>
</code></pre></div>
<p>最后修改两个 TCP Socket 的 ip 和端口为自己测试服务器的 ip 和端口：</p>
<div class="highlight"><pre><span></span><code><span class="n">socket_id_0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tos_sal_module_connect</span><span class="p">(</span><span class="s">&quot;117.50.111.72&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;8080&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">TOS_SAL_PROTO_TCP</span><span class="p">);</span>
<span class="n">socket_id_1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tos_sal_module_connect</span><span class="p">(</span><span class="s">&quot;117.50.111.72&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;8001&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">TOS_SAL_PROTO_TCP</span><span class="p">);</span>
</code></pre></div>
<blockquote>
<p>TCP 测试服务器需要自己搭建或者使用一些小工具，此处不再详述。</p>
</blockquote>
<p>修改完成之后，编译程序，烧录到开发板中，在串口助手中查看结果：</p>
<p><img alt="tcp_example_result_uart" src="../assets/AT_Firmware/tcp_example_result_uart.png" /></p>
<p>在 socket0 的服务端查看模组发送的消息：</p>
<p><img alt="tcp_example_result_server1" src="../assets/AT_Firmware/tcp_example_result_server1.png" /></p>
<p>在 socket1 的服务端查看模组发送的消息：</p>
<p><img alt="tcp_example_result_server2" src="../assets/AT_Firmware/tcp_example_result_server2.png" /></p>
<p>至此，测试完成。</p>
<h1 id="5">5. 如何适配一个新的通信模组驱动</h1>
<p>基于模组的 AT 指令集，使用 TobudOSAT 框架与模组交互，实现 SAL 框架所定义的函数，这个过程称为通信模组适配。</p>
<p>本文中我以移远通信的 4G 通信模组 EC20 作为示例，讲述一个全新的通信模组适配流程。</p>
<h2 id="step1-at">Step1. 使用串口助手调试，熟悉该模组的 AT 指令集</h2>
<p>适配 SAL 层需要熟悉模组的三类 AT 指令：</p>
<ul>
<li>基本查询配置指令</li>
<li>TCP/IP网络协议栈AT指令</li>
<li>TCP/IP网络协议栈数据接收机制</li>
</ul>
<p>比如 EC20 相关的 AT 指令如下：</p>
<h3 id="1_1">① 基本查询配置指令</h3>
<ul>
<li>测试 AT 指令是否正常？</li>
</ul>
<div class="highlight"><pre><span></span><code>AT

OK
</code></pre></div>
<ul>
<li>查询 SIM 卡是否正常？</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">AT</span><span class="o">+</span><span class="n">CPIN</span><span class="o">?</span>

<span class="o">+</span><span class="n">CPIN</span><span class="o">:</span><span class="w"> </span><span class="n">READY</span>

<span class="n">OK</span>
</code></pre></div>
<ul>
<li>查询模组的信号强度</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">AT</span><span class="o">+</span><span class="n">CSQ</span>

<span class="o">+</span><span class="n">CSQ</span><span class="o">:</span><span class="w"> </span><span class="mi">17</span><span class="p">,</span><span class="mi">0</span>

<span class="n">OK</span>
</code></pre></div>
<ul>
<li>查询模组是否注册到 GSM 网络</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+CREG?

+CREG: 0,1

OK
</code></pre></div>
<ul>
<li>查询模组是否注册上 GPRS 网络</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+CGREG?

+CGREG: 0,1

OK
</code></pre></div>
<ul>
<li>设置 GPRS 的 APN</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+QICSGP=1,1,&quot;CMNET&quot;

OK
</code></pre></div>
<ul>
<li>激活移动场景</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+QIACT=1

OK
</code></pre></div>
<h3 id="2-tcpipat">② TCP/IP网络协议栈AT指令</h3>
<p>TCP/IP网络协议栈至少需要TCP/UDP socket 的通信 AT 指令，其它上层协议的 AT 指令暂时不用。</p>
<ul>
<li>建立 Socket</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+QIOPEN=1,0,&quot;TCP&quot;,&quot;117.50.111.72&quot;,8902,0,0

OK

+QIOPEN: 0,0
</code></pre></div>
<blockquote>
<p>在建立 socket 的时候需要注意，有的通信模组需要提前使用 AT 指令配置单链路模式还是多链路模式（eg. ESP8266）,而有的通信模组默认直接支持多链路模式，无需配置。</p>
</blockquote>
<ul>
<li>发送数据</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">AT</span><span class="o">+</span><span class="n">QISEND</span><span class="o">=</span><span class="mi">0</span>

<span class="o">&gt;</span><span class="w"> </span><span class="n">hello</span><span class="o">&lt;</span><span class="mh">0x1a</span><span class="o">&gt;</span>

<span class="n">SEND</span><span class="w"> </span><span class="n">OK</span>
</code></pre></div>
<blockquote>
<p>发送数据的时候需要注意，有的通信模组发送数据使用 ASCII 字符 (eg. ESP8266、EC20 等)，而有的通信模组发送数据使用十六进制（eg. NB-IoT 类模组）</p>
</blockquote>
<ul>
<li>接收数据</li>
</ul>
<p>接下来的一节重点讲述。</p>
<ul>
<li>关闭 Socket</li>
</ul>
<div class="highlight"><pre><span></span><code>AT+QICLOSE=0

OK
</code></pre></div>
<h3 id="3-tcpip">③ TCP/IP网络协议栈数据接收机制</h3>
<p>通信模组在接收到服务器发来的数据时，会有两种方式上报给 MCU：</p>
<ul>
<li>使用固定的 IP 头上报 socket id 和数据长度，需要再次去读取数据 (缓冲模式接收)</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="c1">//模组上报</span>
<span class="o">+</span><span class="n">QIURC</span><span class="o">:</span><span class="w"> </span><span class="s">&quot;recv&quot;</span><span class="p">,</span><span class="mi">0</span>

<span class="c1">//MCU 发出 AT 指令去读取数据</span>
<span class="n">AT</span><span class="o">+</span><span class="n">QIRD</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="mi">1500</span>

<span class="o">+</span><span class="n">QIRD</span><span class="o">:</span><span class="w"> </span><span class="mi">14</span>
<span class="n">Hello</span><span class="p">,</span><span class="w"> </span><span class="n">client</span><span class="o">!</span>

<span class="n">OK</span>
</code></pre></div>
<ul>
<li>使用固定的 IP 头上报 socket id 和数据长度，同时一起上报数据 (直接模式接收)</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="o">+</span><span class="n">QIURC</span><span class="o">:</span><span class="w"> </span><span class="s">&quot;recv&quot;</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">14</span>
<span class="n">Hello</span><span class="p">,</span><span class="w"> </span><span class="n">client</span><span class="o">!</span>
</code></pre></div>
<p>有的模组只支持某一种模式，有的模组两种模式都支持，可以自己配置，对于 TobudOS的 AT 框架来说，第二种直接上报模式解析起来会更加方便。</p>
<h2 id="step2-at">Step2. 三类 AT 指令，三种实现方式</h2>
<p>上面讲述了适配 SAL 层需要熟悉模组的三类 AT 指令：基本查询配置指令、TCP/IP 网络协议栈 AT 指令、TCP/IP 网络协议栈数据接收机制，这节讲述如何使用 AT 框架实现这三类 AT 指令。</p>
<p>① 只需要判断是否返回 OK 的 AT 指令</p>
<p>这一类 AT 指令的实现函数除了指令内容不同，别的都相同，比如关闭回显的 AT 指令：</p>
<div class="highlight"><pre><span></span><code>ATE0

OK
</code></pre></div>
<p>对应的实现方法如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">ec20_echo_close</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">at_echo_t</span><span class="w"> </span><span class="n">echo</span><span class="p">;</span>

<span class="w">    </span><span class="cm">/* 创建一个 echo 对象，缓冲区为 NULL，期望字符串为 NULL */</span>
<span class="w">    </span><span class="n">tos_at_echo_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span>

<span class="w">    </span><span class="cm">/* 执行 AT 命令，超时时长 1000ms */</span>
<span class="w">    </span><span class="n">tos_at_cmd_exec</span><span class="p">(</span><span class="o">&amp;</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="mi">1000</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;ATE0</span><span class="se">\r\n</span><span class="s">&quot;</span><span class="p">);</span>

<span class="w">    </span><span class="cm">/* 判断执行结果是否为 OK */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">echo</span><span class="p">.</span><span class="n">status</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">AT_ECHO_STATUS_OK</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>② 需要判断是否 OK，也需要解析执行结果的 AT 指令</p>
<p>这一类 AT 指令的实现函数中，不同点在于，创建 echo 对象的时候需要传入一个 buffer 来存放指令执行的结果，比如查询信号强度的 AT 指令：</p>
<div class="highlight"><pre><span></span><code>AT+CSQ

+CSQ: 17,0

OK
</code></pre></div>
<p>对应的实现方法如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">ec20_signal_quality_check</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">rssi</span><span class="p">,</span><span class="w"> </span><span class="n">ber</span><span class="p">;</span>
<span class="w">    </span><span class="n">at_echo_t</span><span class="w"> </span><span class="n">echo</span><span class="p">;</span>
<span class="w">    </span><span class="kt">char</span><span class="w"> </span><span class="n">echo_buffer</span><span class="p">[</span><span class="mi">32</span><span class="p">],</span><span class="w"> </span><span class="o">*</span><span class="n">str</span><span class="p">;</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">try</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>

<span class="w">    </span><span class="cm">/* 创建 echo 对象，传入一个缓冲区存放 AT 命令执行结果 */</span>
<span class="w">    </span><span class="n">tos_at_echo_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="n">echo_buffer</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">echo_buffer</span><span class="p">),</span><span class="w"> </span><span class="nb">NULL</span><span class="p">);</span>

<span class="w">    </span><span class="cm">/* 尝试检测 10 次，一旦有一次正常，返回 */</span>
<span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">try</span><span class="o">++</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="cm">/* 执行 AT 命令，超时时长 1000ms */</span>
<span class="w">        </span><span class="n">tos_at_cmd_exec</span><span class="p">(</span><span class="o">&amp;</span><span class="n">echo</span><span class="p">,</span><span class="w"> </span><span class="mi">1000</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;AT+CSQ</span><span class="se">\r\n</span><span class="s">&quot;</span><span class="p">);</span>

<span class="w">        </span><span class="cm">/* 判断执行结果是否返回了 OK */</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">echo</span><span class="p">.</span><span class="n">status</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">AT_ECHO_STATUS_OK</span><span class="p">)</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="cm">/* 从 AT 指令的执行结果中解析提取 CSQ 值进行判断 */</span>
<span class="w">        </span><span class="n">str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">strstr</span><span class="p">(</span><span class="n">echo</span><span class="p">.</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;+CSQ:&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="n">sscanf</span><span class="p">(</span><span class="n">str</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;+CSQ:%d,%d&quot;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">rssi</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">ber</span><span class="p">);</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">rssi</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">99</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>③ 模组主动上报的数据处理</p>
<p>这一类AT指令对应TCP/IP协议栈接收数据的上报机制，使用AT框架的事件机制进行处理。</p>
<p>首先将固定的 ip 头和事件处理回调函数注册：</p>
<div class="highlight"><pre><span></span><code><span class="n">at_event_t</span><span class="w"> </span><span class="n">ec20_at_event</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">{</span><span class="w"> </span><span class="s">&quot;+QIURC: </span><span class="se">\&quot;</span><span class="s">recv</span><span class="se">\&quot;</span><span class="s">,&quot;</span><span class="p">,</span><span class="w">   </span><span class="n">ec20_incoming_data_process</span><span class="p">},</span><span class="w">   </span><span class="c1">//处理远程服务器发来的数据</span>
<span class="w">    </span><span class="p">{</span><span class="w"> </span><span class="s">&quot;+QIURC: </span><span class="se">\&quot;</span><span class="s">dnsgip</span><span class="se">\&quot;</span><span class="s">,&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">ec20_domain_data_process</span><span class="p">},</span><span class="w">     </span><span class="c1">//处理域名解析结果，暂时不管</span>
<span class="p">};</span>
</code></pre></div>
<p>事件处理回调函数自己编写，主要作用是提取模组上报的 scoketid、数据长度、数据内容，然后将数据内容写入到对应 socket id 的 channel 中。</p>
<blockquote>
<p>需要注意，AT 框架一旦读取解析到固定的 IP 头，则停止解析，拉起对应的回调函数，所以在回调函数中可以继续从缓冲区中一边读取一边解析。</p>
</blockquote>
<p>解析示例如下：</p>
<div class="highlight"><pre><span></span><code><span class="n">__STATIC__</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">ec20_incoming_data_process</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">data</span><span class="p">;</span>
<span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">channel_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">data_len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">read_len</span><span class="p">;</span>
<span class="w">    </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">buffer</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>

<span class="w">    </span><span class="cm">/*</span>
<span class="cm">        模组上报的数据格式：</span>
<span class="cm">        +QIURC: &quot;recv&quot;,&lt;sockid&gt;,&lt;datalen&gt;</span>
<span class="cm">        &lt;data content&gt;</span>
<span class="cm">    */</span>

<span class="w">    </span><span class="cm">/* 注册的 ip 头是：[+QIURC: &quot;recv&quot;,] 回调函数被拉起执行后，接着处理后边的数据即可 */</span>

<span class="w">    </span><span class="cm">/* 读取解析 socket id */</span>
<span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_uart_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">&#39;,&#39;</span><span class="p">)</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="k">break</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="n">channel_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">channel_id</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="sc">&#39;0&#39;</span><span class="p">);</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 读取解析数据长度 */</span>
<span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_uart_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">&#39;\r&#39;</span><span class="p">)</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="k">break</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="n">data_len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data_len</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="sc">&#39;0&#39;</span><span class="p">);</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 读取&#39;\r&#39;之后的&#39;\n&#39;，不作任何处理 */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_uart_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 根据解析出的数据长度和缓冲区的长度，循环读取数据内容，写入到对应 socket id 的 channel 中 */</span>
<span class="w">    </span><span class="k">do</span><span class="w"> </span><span class="p">{</span>
<span class="cp">#define MIN(a, b)   ((a) &lt; (b) ? (a) : (b))</span>
<span class="w">        </span><span class="n">read_len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MIN</span><span class="p">(</span><span class="n">data_len</span><span class="p">,</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">buffer</span><span class="p">));</span>

<span class="w">        </span><span class="cm">/* 读取数据 */</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_uart_read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="n">read_len</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">read_len</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="cm">/* 写入到对应的 channel 中 */</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_channel_write</span><span class="p">(</span><span class="n">channel_id</span><span class="p">,</span><span class="w"> </span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="n">read_len</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="n">data_len</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="n">read_len</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">data_len</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span>

<span class="w">    </span><span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="step3">Step3. 整体适配流程</h2>
<p>前两步都是细节性的处理，这一步从整体的角度讲述适配流程。</p>
<p>① 模组初始化</p>
<div class="highlight"><pre><span></span><code><span class="k">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">ec20_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;Init ec20 ...</span><span class="se">\n</span><span class="s">&quot;</span><span class="w"> </span><span class="p">);</span>

<span class="w">    </span><span class="cm">/* 关闭回显 */</span>

<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">ec20_echo_close</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;echo close failed,please check your module</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 检测 SIM 卡是否正常 */</span>
<span class="w">    </span><span class="k">if</span><span class="p">(</span><span class="n">ec20_sim_card_check</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;sim card check failed,please insert your card</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 检测信号强度是否正常 */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">ec20_signal_quality_check</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;signal quality check status failed</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 检测是否注册到 GSM 网络 */</span>
<span class="w">    </span><span class="k">if</span><span class="p">(</span><span class="n">ec20_gsm_network_check</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;GSM network register status check fail</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 检测是否注册到 GPRS 网络 */</span>
<span class="w">    </span><span class="k">if</span><span class="p">(</span><span class="n">ec20_gprs_network_check</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;GPRS network register status check fail</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 关闭 APN */</span>
<span class="w">    </span><span class="k">if</span><span class="p">(</span><span class="n">ec20_close_apn</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w">    </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;close apn failed</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 设置 APN，激活移动场景 */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">ec20_set_apn</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;apn set FAILED</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">);</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="n">printf</span><span class="p">(</span><span class="s">&quot;Init ec20 ok</span><span class="se">\n</span><span class="s">&quot;</span><span class="w"> </span><span class="p">);</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>② 将实现的函数映射到 SAL 框架上：</p>
<div class="highlight"><pre><span></span><code><span class="n">sal_module_t</span><span class="w"> </span><span class="n">sal_module_ec20</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">.</span><span class="n">init</span><span class="w">             </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_init</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">connect</span><span class="w">          </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_connect</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">send</span><span class="w">             </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_send</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">recv_timeout</span><span class="w">     </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_recv_timeout</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">recv</span><span class="w">             </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_recv</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">sendto</span><span class="w">           </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_sendto</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">recvfrom</span><span class="w">         </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_recvfrom</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">recvfrom_timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_recvfrom_timeout</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">close</span><span class="w">            </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_close</span><span class="p">,</span>
<span class="w">    </span><span class="p">.</span><span class="n">parse_domain</span><span class="w">     </span><span class="o">=</span><span class="w"> </span><span class="n">ec20_parse_domain</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div>
<p>③ 再次封装，留出一个外部调用接口，供上层应用程序调用：</p>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">ec20_sal_init</span><span class="p">(</span><span class="n">hal_uart_port_t</span><span class="w"> </span><span class="n">uart_port</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="cm">/* 初始化 AT 框架及其串口 */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_at_init</span><span class="p">(</span><span class="n">uart_port</span><span class="p">,</span><span class="w"> </span><span class="n">ec20_at_event</span><span class="p">,</span>
<span class="w">                        </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ec20_at_event</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">ec20_at_event</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 将第②步中映射的函数关系，注册到 SAL 框架 */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_sal_module_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sal_module_ec20</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="cm">/* 调用 SAL 初始化函数，因为接口和映射的存在，最终调用到 ec20_init */</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">tos_sal_module_init</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="mi">-1</span><span class="p">;</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>





                
              </article>
            </div>
          
          
        </div>
        
          <button type="button" class="md-top md-icon" data-md-component="top" hidden>
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12Z"/></svg>
            Back to top
          </button>
        
      </main>
      
        <footer class="md-footer">
  
  <div class="md-footer-meta md-typeset">
    <div class="md-footer-meta__inner md-grid">
      <div class="md-copyright">
  
    <div class="md-copyright__highlight">
      Copyright &copy; 2023 TobudOS
    </div>
  
  
    Made with
    <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
      Material for MkDocs
    </a>
  
</div>
      
    </div>
  </div>
</footer>
      
    </div>
    <div class="md-dialog" data-md-component="dialog">
      <div class="md-dialog__inner md-typeset"></div>
    </div>
    
    <script id="__config" type="application/json">{"base": "../../..", "features": ["content.action.edit", "content.action.view", "content.code.annotate", "content.tooltips", "navigation.indexes", "navigation.sections", "navigation.tabs", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.74e28a9f.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
    
    
      <script src="../../../assets/javascripts/bundle.220ee61c.min.js"></script>
      
    
  </body>
</html>